// Copyright (c) 2010 SuccessFactors, Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
//     * Redistributions of source code must retain the above
//       copyright notice, this list of conditions and the following
//       disclaimer.
//
//     * Redistributions in binary form must reproduce the above
//       copyright notice, this list of conditions and the following
//       disclaimer in the documentation and/or other materials
//       provided with the distribution.
//
//     * Neither the name of the SuccessFactors, Inc. nor the names of
//       its contributors may be used to endorse or promote products
//       derived from this software without specific prior written
//       permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
// OF THE POSSIBILITY OF SUCH DAMAGE.

package org.owasp.jxt;

import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * TranslateContext
 *
 * @author Jeffrey Ichnowski
 * @version $Revision: 8 $
 */
public final class TranslateContext {
    private static final String VARPREFIX = "__jxt";
    private static final int DEFAULT_INDENT = 4;

    private JxtEngine _engine;

    private PrintWriter _out;

    private StringBuilder _line = new StringBuilder();
    private StringBuilder _data = new StringBuilder();

    private String _rootSourceFile;
    private String _targetClassName;
    private Set<String> _sourceFiles;

    private int _indentSize = DEFAULT_INDENT;
    private int _indentLevel;

    // TODO: revisit this.  The root encoding (pushRuntimeEncoder and
    // popRuntimeEncoder) is carried through servlet includes, but is
    // inefficient.  The _textEncoder and _exprEncoder are efficient,
    // but not carried through includes at runtime.  Fortunately this
    // supports the most common usecases, but this is a MAJOR gap and
    // needs to be fixed.

    /** The root node of this translation to source.  It is either a
     * CorePageNode or CoreTemplateNode and provides the runtime
     * encoding switch. */
    private JxtRoot _root;

    /** The "text" encoder is used to encode text nodes--any text
     * appearing as regular content in a page.  Text nodes are
     * template provided text and used to demark syntatic and semantic
     * elements of the content (e.g. future revisions will use the
     * text context to distinguish JavaScript string data from numeric
     * data, etc...).  */
    private Encoder _textEncoder = Encoder.XML_CONTENT;

    /** The "expr" encoder is used to encode expression nodes that
     * appear as content on the page.  Expression nodes are data
     * driven elements of the page.  They are purely data, and do not
     * do syntatic or semantic context switches (e.g. future revisions
     * will have this context driven by text boundaries). */
    private Encoder _exprEncoder = Encoder.XML_CONTENT;

    private Map<String,int[]> _varNameCounts = new HashMap<String,int[]>();
    private Map<String,Object> _attributes = new HashMap<String,Object>();

    public TranslateContext(
        JxtEngine engine,
        JxtRoot root,
        String rootSourceFile,
        String targetClassName,
        Set<String> sourceFiles,
        PrintWriter out)
    {
        _engine = engine;
        _root = root;
        _rootSourceFile = rootSourceFile;
        _targetClassName = targetClassName;
        _sourceFiles = sourceFiles;
        _out = out;
    }

    public String getRootSourceFile() {
        return _rootSourceFile;
    }

    public Set<String> getSourceFiles() {
        return _sourceFiles;
    }

    public String getTargetClassName() {
        return _targetClassName;
    }

    public void setAttribute(String name, Object value) {
        _attributes.put(name, value);
    }

    public Object getAttribute(String name) {
        return _attributes.get(name);
    }

    public String generateVariableName(String part) {
        part = part.replaceAll("[^_a-zA-Z0-9]", "_");
        int[] holder = _varNameCounts.get(part);
        if (holder == null) {
            holder = new int[] { 0 };
            _varNameCounts.put(part, holder);
        }
        return VARPREFIX + part + (++holder[0]);
    }

    private void printIndent() {
        for (int i=0 ; i<_indentLevel ; ++i) {
            _line.append(' ');
        }
    }

    private void maybeIndent() {
        if (_line.length() == 0) {
            printIndent();
        }
    }

    private void flushLine() {
        _out.println(_line);
        _line.setLength(0);
    }


    private void flushData() {
        if (_data.length() == 0) {
            return;
        }

        String data = _data.toString();

        // must setLength(0) here before continuing, or it will
        // infinite recurse since we're about to call "code()"
        _data.setLength(0);

        codeln("out.write(\""+Encoder.JAVA_STRING.apply(data)+"\");");
    }

    public void codeln() {
        flushLine();
    }

    public void code(String code) {
        flushData();
        maybeIndent();
        _line.append(code);
    }

    public void codeln(String code) {
        code(code);
        flushLine();
    }

    public void data(String data) {
        _data.append(data);
    }

    public void indent() {
        flushData();
        _indentLevel += _indentSize;
    }

    public void outdent() {
        flushData();
        _indentLevel -= _indentSize;
    }

    public void finish() {
        flushData();
        if (_line.length() > 0) {
            flushLine();
        }
        _out.flush();
    }

    public void setTextEncoder(Encoder enc) {
        _textEncoder = enc;
    }

    public Encoder getTextEncoder() {
        return _textEncoder;
    }

    public void setExprEncoder(Encoder enc) {
        _exprEncoder = enc;
    }

    public Encoder getExprEncoder() {
        return _exprEncoder;
    }

    public void pushRuntimeEncoder(Encoder enc) {
        _root.pushRuntimeEncoder(this, enc);
    }

    public void popRuntimeEncoder() {
        _root.popRuntimeEncoder(this);
    }

    public void translateLocationComment(JxtNode node) {
        if (node.getSystemId() != null && node.getLineNumber() > 0) {
            codeln("// "+node.getSystemId()+" line "+node.getLineNumber());
        }
    }

} // TranslateContext
